##  Cate - Copyright (C) 2006 - Marco Bubke
##  This program is free software; you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation; either version 2, or (at your option)
##  any later version.
##  
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.

import sys
from PyQt4 import QtCore, QtGui, Qsci

import openglview
import opengl_object
import shader
import inspect
import zipfile

from elementtree.ElementTree import Element, SubElement, ElementTree, tostring, fromstring

import ui

try:
  from OpenGL import GL
except ImportError:
  app = QtGui.QApplication(sys.argv)
  QtGui.QMessageBox.critical(None, "Cate", 
    "PyOpenGL must be installed to run this example.", 
    QtGui.QMessageBox.Ok | QtGui.QMessageBox.Default,
    QtGui.QMessageBox.NoButton)
  sys.exit(1)

__all__ = ['MainWindow']


class SourceCodeEditorGLSL(Qsci.QsciScintilla):
  def __init__(self, parent=None, source=None):
    Qsci.QsciScintilla.__init__(self, parent)
    if source != None:
      self.setText(source)
    self.setAutoIndent(True)
    self.setIndentationsUseTabs(False)
    self.setIndentationWidth(2)
    self.setTabWidth(2)
    self.setAutoCompletionSource(Qsci.QsciScintilla.AcsAll)
    self.setAutoCompletionThreshold(3)
    self.setMarginLineNumbers(1, True)
    self.setEolMode(self.EolUnix) 
    
    lexer =  Qsci.QsciLexerCPP(self)
    lexer.setAutoIndentStyle(Qsci.QsciScintilla.AiMaintain|
      Qsci.QsciScintilla.AiOpening|
      Qsci.QsciScintilla.AiClosing)
    self.setLexer(lexer)
    self.connect(self, QtCore.SIGNAL("textChanged()"), self.onChangedText)
    
  def onChangedText(self):
    self.emit(QtCore.SIGNAL("textChangedWithString"), self.text())
    
  def loadAPI(self, apiFileName):
    self.lexer().apiLoad(apiFileName)

    
class SourceCodeEditorPython(Qsci.QsciScintilla):
  def __init__(self, name, parent=None, source=None):
    Qsci.QsciScintilla.__init__(self, parent)
    if source != None:
      self.setText(source)
    self.name = name
    self.setAutoIndent(True)
    self.setIndentationsUseTabs(False)
    self.setIndentationWidth(2)
    self.setTabWidth(2)
    self.setAutoCompletionSource(Qsci.QsciScintilla.AcsAll) 
    self.setAutoCompletionThreshold(3)
    self.setMarginLineNumbers(1, True)
    self.setEolMode(self.EolUnix)
    

    lexer =  Qsci.QsciLexerPython(self)
    lexer.setAutoIndentStyle(Qsci.QsciScintilla.AiMaintain|
      Qsci.QsciScintilla.AiOpening|
      Qsci.QsciScintilla.AiClosing)
    self.setLexer(lexer)
    
    self.connect(self, QtCore.SIGNAL("textChanged()"), self.onChangedText)
  

  def onChangedText(self): 
    self.emit(QtCore.SIGNAL("textChangedWithString"), self.text())
    
  def loadAPI(self, apiFileName):
    self.lexer().apiLoad(apiFileName)
    
  def addAPI(self, apiDict):
    lexer = self.lexer()
    for item in apiDict.iteritems():
        lexer.apiAdd(item[0])
        
  def addAPIItem(self, item):
    lexer = self.lexer()
    lexer.apiAdd(item)   
    
class MessageWidget(QtGui.QTextEdit):
  def __init__(self, parent=None):
    QtGui.QTextEdit.__init__(self, parent)
    self.setReadOnly(True)
    
  def sizeHint(self):
    return QtCore.QSize(700, 300) 
    
class MainWindow(QtGui.QMainWindow):
  def __init__(self, parent=None):    
    QtGui.QMainWindow.__init__(self, parent)
    
    self.createOpenGLDock()
    self.createObjectsDock()
    self.createCentralEditor()
    self.createMessageDock()
    self.createMenus()
    
    self.projectFileName = '/tmp/test.cate'

    self.openGLObjects = {}
    self.shaderObjects = {}

  def createMenus(self):
    menuBar = self.menuBar()
    
    fileMenu = menuBar.addMenu(self.tr("&File"))
    fileMenu.addAction(self.tr("&Open"), self.openProject)
    fileMenu.addAction(self.tr("&Save"), self.saveProject)
    fileMenu.addSeparator()
    fileMenu.addAction(self.tr("Import Vertex Shader"), 
      self.importVertexShader)
    fileMenu.addAction(self.tr("Export Vertex Shader"))
    fileMenu.addSeparator()    
    fileMenu.addAction(self.tr("Import Fragment Shader")) 
    fileMenu.addAction(self.tr("Export Fragment Shader"))
    fileMenu.addSeparator()
    fileMenu.addAction(self.tr("Import Model"))
    
    self.editorAction = {}    
    
    viewMenu = menuBar.addMenu(self.tr("&Source Editor"))

    self.createCheckedMenuIn(viewMenu, self.paintSourceEditor, 
      self.showCodeEditor, self.tr("&Show Paint Code Editor"),
      'showTabInCodeTabWidget(bool)')       
      
    self.createCheckedMenuIn(viewMenu, self.resizeSourceEditor, 
      self.showCodeEditor, self.tr("&Show Resize Code Editor"),
      'showTabInCodeTabWidget(bool)') 

    self.createCheckedMenuIn(viewMenu, self.initSourceEditor, 
      self.showCodeEditor, self.tr("&Show Init Code Editor"),
      'showTabInCodeTabWidget(bool)')
      
    self.shaderMenu = menuBar.addMenu(self.tr("&Shader Editor"))
    

    

  def createCheckedMenuIn(self, menu, editor, showMethod, label, signal):
    action = menu.addAction(label)
    action.editor = editor
    self.connect(action, QtCore.SIGNAL('toggled(bool)'), showMethod)
    action.setCheckable(True)
    action.setChecked(True)
    self.connect(editor, QtCore.SIGNAL(signal), action, 
      QtCore.SLOT('setChecked(bool)'))
    self.editorAction[editor.name] = action
      
  def createOpenGLDock(self):
    openGLDock = QtGui.QDockWidget(self.tr("OpenGLViewer"), self)
    self.openGLView = openglview.OpenGLView(self);
    openGLDock.setWidget(self.openGLView)
    openGLDock.setMinimumSize(300,300)
    openGLDock.setSizePolicy(QtGui.QSizePolicy.Expanding, 
      QtGui.QSizePolicy.Expanding)
    self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, openGLDock)

  def createObjectsDock(self):
    dock = QtGui.QDockWidget(self.tr("Object"), self)
    self.objectsTabWidget = QtGui.QTabWidget(self);
    dock.setWidget(self.objectsTabWidget)
    dock.setMinimumSize(300,300)
    dock.setSizePolicy(QtGui.QSizePolicy.Expanding, 
      QtGui.QSizePolicy.Expanding)
    self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)

  def createCentralEditor(self):
    self.centralTabWidget = QtGui.QTabWidget(self)
    self.codeTabWidget = QtGui.QTabWidget(self.centralTabWidget)
    self.shaderTabWidget = QtGui.QTabWidget(self.centralTabWidget)
    
    self.centralTabWidget.addTab(self.codeTabWidget, 'Source Code')
    self.centralTabWidget.addTab(self.shaderTabWidget, 'Shader Code')
    
    self.paintSourceEditor = SourceCodeEditorPython('Paint Code', 
      self.codeTabWidget)
    self.resizeSourceEditor = SourceCodeEditorPython('Resize Code', 
      self.codeTabWidget)
    self.initSourceEditor = SourceCodeEditorPython('Init Code', 
      self.codeTabWidget)
       
    self.setCentralWidget(self.centralTabWidget)
    
    destroyButton = QtGui.QPushButton(self.codeTabWidget)
    destroyButton.setIcon(QtGui.QIcon(':/icons/delete.png'))
    destroyButton.setFlat(True)
    self.codeTabWidget.setCornerWidget(destroyButton)
    self.connect(destroyButton, QtCore.SIGNAL('clicked()'), 
      self.destroyCurrentTabInCodeTabWidget)

    destroyButton = QtGui.QPushButton(self.shaderTabWidget)
    destroyButton.setIcon(QtGui.QIcon(':/icons/delete.png'))
    destroyButton.setFlat(True)
    self.shaderTabWidget.setCornerWidget(destroyButton)
    self.connect(destroyButton, QtCore.SIGNAL('clicked()'), 
      self.destroyCurrentTabInShaderTabWidget)

    self.connect(self.openGLView, QtCore.SIGNAL("paintCodeError"), 
      self.errorInPaintCode)
    self.connect(self.openGLView, QtCore.SIGNAL("resizeCodeError"), 
      self.errorInPaintCode)  
    self.connect(self.openGLView, QtCore.SIGNAL("initCodeError"), 
      self.errorInPaintCode)  
    self.connect(self.openGLView, QtCore.SIGNAL("initialized()"), 
      self.connectEditorsWithShaders)
    self.connect(self.openGLView, QtCore.SIGNAL("initialized()"), 
      self.getSourceCodeFromFile)
    self.connect(self.openGLView, QtCore.SIGNAL("globalNameSpacePopulated"), 
      self.populatePythonSourceCodeAPIs)      
    self.connect(self.openGLView, QtCore.SIGNAL("localNameSpacePopulated"), 
      self.populatePythonSourceCodeAPIs)      
    self.connect(self.openGLView, QtCore.SIGNAL("newLocalNameSpaceItem"), 
      self.addItemToPythonSourceCodeAPIs)     
    self.connect(self.openGLView, QtCore.SIGNAL("newLocalNameSpaceItem"), 
      self.createObjectUI)  
    self.connect(self.openGLView, QtCore.SIGNAL("deleteLocalNameSpaceItem"), 
      self.destroyObjectUI)      

  def populatePythonSourceCodeAPIs(self, apiDict):
    self.paintSourceEditor.addAPI(apiDict)
    self.resizeSourceEditor.addAPI(apiDict)
    self.initSourceEditor.addAPI(apiDict)
    
  def addItemToPythonSourceCodeAPIs(self, item, value):
    self.paintSourceEditor.addAPIItem(item)
    self.resizeSourceEditor.addAPIItem(item)
    self.initSourceEditor.addAPIItem(item)   

  def connectEditorsWithShaders(self):
    self.connect(self.openGLView.getLocalNameSpace('fragmentShader'), 
      QtCore.SIGNAL("shaderLog"), 
      self.messageWidget.setPlainText)
    self.connect(self.openGLView.getLocalNameSpace('vertexShader'), 
      QtCore.SIGNAL("shaderLog"), 
      self.messageWidget.setPlainText)      
    self.connect(self.openGLView.getLocalNameSpace('program'), 
      QtCore.SIGNAL("shaderLog"), 
      self.messageWidget.setPlainText)
  
    self.connect(self.initSourceEditor, 
      QtCore.SIGNAL("textChangedWithString"), 
      self.compileInitCode)    
    self.connect(self.resizeSourceEditor, 
      QtCore.SIGNAL("textChangedWithString"), 
      self.compileResizeCode)     
    self.connect(self.paintSourceEditor, 
      QtCore.SIGNAL("textChangedWithString"), 
      self.compilePaintCode)  
     
    

  def getSourceCodeFromFile(self): 
    sourceFile = open('share/init_code.py', 'rU')
    sourceCode = sourceFile.read()
    self.initSourceEditor.setText(sourceCode)

    sourceFile = open('share/resize_code.py', 'rU')
    sourceCode = sourceFile.read()
    self.resizeSourceEditor.setText(sourceCode)
   
    sourceFile = open('share/paint_code.py', 'rU')
    sourceCode = sourceFile.read()
    self.paintSourceEditor.setText(sourceCode)
    


  def createMessageDock(self):
    messageDock = QtGui.QDockWidget(self.tr("Messages"), self)
    self.messageWidget = MessageWidget(messageDock)
    messageDock.setWidget(self.messageWidget)
    messageDock.setSizePolicy(QtGui.QSizePolicy.Expanding, 
      QtGui.QSizePolicy.Expanding)
    self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, messageDock)
  
  def changeXMLElementText(self, root, elem, text):
    node = root.find(elem)
    try:
      node.text = str(text)
    except AttributeError:
      node = SubElement(root, elem)
      node.text = str(text)
    return node  
      
  def syncXML(self, root):
    self.changeXMLElementText(root, 'init', self.initSourceEditor.text())
    self.changeXMLElementText(root, 'resize', self.resizeSourceEditor.text())
    self.changeXMLElementText(root, 'paint', self.paintSourceEditor.text())
    objects = self.changeXMLElementText(root, 'objects','Here you find the all OpenGl objects')
    self.openGLView.changeLocalNameSpaceXMLTree(objects)
    
    
  def saveProject(self):
    self.saveAsProject(self.projectFileName)
    
  def saveAsProject(self, fileName):
    try:
      projectZipFile = zipfile.ZipFile(self.projectFileName, mode='r')
      try:
        treeString = projectZipFile.read('cate.xml')
        root = fromstring(treeString)
      except KeyError:
        root = Element('cate')
    except IOError:
      root = Element('cate')
    print tostring(root)

    self.syncXML(root)
    projectZipFile = zipfile.ZipFile(self.projectFileName, mode='w')
    projectZipFile.writestr('cate.xml', tostring(root))
    
  def openProject(self):
    self.projectFile = zipfile.ZipFile(self.projectFileName, 'r')
    tree = ElementTree(file=self.projectFile)
    self.projectXML = tree.getroot()
    self.projectXML.find('init')
    
  def importVertexShader(self):
    fileName = QtGui.QFileDialog.getOpenFileName(
                    self,
                    "Choose a file",
                    "/home",
                    "Vertex Shader (*.glsl *.vert)")
    file = open(fileName, 'r')

  def compilePaintCode(self, source):
    try:
      code = compile(str(source), 'PaintCode', 'exec')
    except SyntaxError:
      self.messageWidget.setPlainText(str(sys.exc_value))
    else:
      self.messageWidget.setPlainText("paint code compiled")
      self.openGLView.paintCode = code
      self.openGLView.updateGL()

  def compileResizeCode(self, source):
    try:
      code = compile(str(source), 'ResizeCode', 'exec')
    except SyntaxError:
      self.messageWidget.setPlainText(str(sys.exc_value))
    else:
      self.messageWidget.setPlainText("resize code compiled")
      self.openGLView.resizeCode = code
      self.openGLView.updateResizeCode()
      self.openGLView.updateGL()
      
  def compileInitCode(self, source):
    try:
      code = compile(str(source), 'InitCode', 'exec')
    except SyntaxError:
      self.messageWidget.setPlainText(str(sys.exc_value))
    else:
      self.messageWidget.setPlainText("init code compiled")
      self.openGLView.initCode = code
      self.openGLView.updateInitCode()
      self.openGLView.updateGL()
      
  def errorInPaintCode(self, exc):
    self.messageWidget.setPlainText(str(exc))
    
  def createObjectUI(self, name, openGLObject):
    if isinstance(openGLObject, opengl_object.OpenGLObject):
      if isinstance(openGLObject, shader.Shader):
        openGLObjectUI = getattr(ui, openGLObject.type)
        openGLObjectWidget = openGLObjectUI(self.shaderTabWidget, 
          openGLObject, self.openGLView)
        self.createCheckedMenuIn(self.shaderMenu, openGLObjectWidget, 
          self.showShaderEditor, 
          '%s %s %s'  % (self.tr('&Show'),
          openGLObjectWidget.name, self.tr('Editor')),
          'showTabInShaderTabWidget(bool)')   
        self.shaderObjects[name] = openGLObjectWidget
      else:
        openGLObjectUI = getattr(ui, openGLObject.type)
        openGLObjectWidget = openGLObjectUI(self.objectsTabWidget, 
          openGLObject, self.openGLView)
        self.objectsTabWidget.addTab(openGLObjectWidget, name)
        self.openGLObjects[name] = openGLObjectWidget
    
  def destroyObjectUI(self, name):
    if name in self.openGLObjects:
      del self.openGLObjects[name]
  
  def showEditor(self, show, editor, tabWidget):
    if show:
      tabWidget.addTab(editor, 
        editor.name)
    else:
      index = tabWidget.indexOf(editor)
      tabWidget.removeTab(index)
      
    
  def showCodeEditor(self, show):
    self.showEditor(show, self.sender().editor, self.codeTabWidget)
    
  def destroyCurrentTabInCodeTabWidget(self):
    widget = self.codeTabWidget.currentWidget()
    widget.emit(QtCore.SIGNAL('showTabInCodeTabWidget(bool)'), False)
      
  def showShaderEditor(self, show):
    self.showEditor(show, self.sender().editor, self.shaderTabWidget)

    
  def destroyCurrentTabInShaderTabWidget(self):    
    widget = self.shaderTabWidget.currentWidget()
    widget.emit(QtCore.SIGNAL('showTabInShaderTabWidget(bool)'), False)    
      
